/*
 * Copyright 2009-2010 Belmont Software Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.belmont.backup.client;

import java.util.*;
import com.google.gwt.core.client.*;
import com.google.gwt.user.client.*;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.user.client.rpc.*;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.event.logical.shared.*;
import com.google.gwt.json.client.*;

public class BackupsPanel extends UIPanel implements ValueChangeHandler<Boolean> {
    JSONArray clientInfo;
    Tree backupTree = new Tree();
    Vector<String[]> clients;
    TextBox backupDirectoryField = new TextBox();
    DeckPanel backupsDeck = new DeckPanel();
    int backupSummaryIndex;
    int backupDetailIndex;
    Label totalHostsField = new Label("Unknown");
    Label totalDiskUsageField = new Label("Unknown");
    HashMap<String, Vector<CheckBox>> cbMap;
    DialogBox confirmDeleteDialog;
    Vector<String> backups;
    Button deleteBackups;

    public BackupsPanel(BackupUI main) {
        super(main, "Administer Backups");
    }

    public Widget init() {
        DockPanel dp = new DockPanel();
        HTML help = new HTML("Shows the available backups for each host.");
        MenuBar menu = new MenuBar(true);

        createBackupSummaryPanel("Show summary", menu, 0);
        menu.addSeparator();
        createBackupDetailPanel("Show details for host", menu, 1);

        createTaskLayout("Backup Tasks:", help, menu, dp, backupsDeck, false, false);

        return dp;
    }

    public void onValueChange(ValueChangeEvent<Boolean> e) {
        CheckBox item = (CheckBox)(e.getSource());
        String fm = item.getFormValue();

        if (fm == null) {
            return;
        }

        int idx = fm.indexOf('/');
        if (idx == -1) {
            return;
        }
        String uuid = fm.substring(0, idx);
        Vector<CheckBox> cv = cbMap.get(uuid);

        if (cv == null) {
            return;
        }

        if (fm.charAt(idx+1) == '%') { 
            // this is a patch checkbox

            if (item.getValue()) {
                int p = cv.indexOf(item);
                if (p != -1) {
                    int l = cv.size();
                    for (int i=p+1; i < l; i++) {
                        CheckBox pc = cv.elementAt(i);
                        if (pc.getFormValue().indexOf('%') != -1) {
                            break;
                        } else {
                            pc.setValue(true);
                        }
                    }
                }
            }
        } else {
            // search back to the enclosing path checkbox
            int pi = cv.indexOf(item);
            CheckBox pathCb = null;
            int pathIdx = -1;
            for (int i= pi - 1; i > 0; i--) {
                CheckBox c = cv.elementAt(i);
                if (c.getFormValue().indexOf('%') != -1) {
                    pathCb = c;
                    pathIdx = i;
                    break;
                }
            }
            if (pathIdx != -1) {
                int s = cv.size();
                boolean all = true;
                if (item.getValue()) {
                    for (int i=pathIdx+1; i < s; i++) {
                        CheckBox c = cv.elementAt(i);
                        if (c.getFormValue().indexOf('%') != -1) {
                            break;
                        }
                        if (! c.getValue()) {
                            all = false;
                            break;
                        }
                    }
                    pathCb.setValue(all);
                } else {
                    pathCb.setValue(false);
                }
            }
        }

        if (item == cv.elementAt(0)) {
            // first item is the one that selects all the subsequent
            // ones 
            if (item.getValue()) {
                // select all the ones in the list
                int l = cv.size();
                for (int i=1; i < l; i++) {
                    cv.elementAt(i).setValue(true);
                }
            }
        } else {
            if (item.getValue()) {
                // check to see if all the rest are selected, if so
                // then select the first one as well
                int l = cv.size();
                boolean all = true;
                for (int i=1; i < l; i++) {
                    if (! cv.elementAt(i).getValue()) {
                        all = false;
                        break;
                    }
                }
                cv.elementAt(0).setValue(all);
            } else {
                cv.elementAt(0).setValue(false);
            }
        }
    }

    public void refresh() {
        main.service.getBackups(new AsyncCallback<String>() {
                public void onFailure(Throwable caught) {
                    main.print("Failed to get backup information.");
                }
                public void onSuccess(String result) {
                    try {
                        JSONValue v = JSONParser.parse(result);
                        if (v == null) {
                            main.print("Failed to parse backup information.");
                            return;
                        }
                        clientInfo = v.isArray();
                        if (clientInfo == null) {
                            main.print("Failed to parse backup information.");
                            return;
                        }
                        populateBackupTree(clientInfo);
                    } catch (Exception ex) {
                        main.print("Failed to parse backup information: "+ex.toString());
                    }
                }
            });
    }

    void createConfirmDeleteDialog() {
        confirmDeleteDialog = new DialogBox(false, true);
        confirmDeleteDialog.setText("Confirm Delete Backups");
        VerticalPanel vp = new VerticalPanel();
        vp.setSpacing(5);
        HorizontalPanel p1 = new HorizontalPanel();
        p1.setSpacing(5);
        p1.add(new Image("/ui/Belmont.png"));
        p1.add(new HTML("<b>Please confirm you want to delete the selected backups</b>"));
        HorizontalPanel p = new HorizontalPanel();
        p.setSpacing(2);
        Button cancel = new Button("Cancel");
        Button ok = new Button("Ok");
        p.add(cancel);
        p.add(ok);
        ok.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                    confirmDeleteDialog.hide();
                    main.print("Deleting backups...");
                    main.service.deleteBackups(backups, new AsyncCallback<Integer>() {
                            public void onFailure(Throwable caught) {
                                main.print("Failed to delete backups.");
                            }
                            public void onSuccess(Integer result) {
                                main.print("Deleted "+result+" backups.");
                            }
                        });
                    confirmDeleteDialog.hide();
                    refresh();
                }
            });
        cancel.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                    confirmDeleteDialog.hide();
                    main.print("Delete cancelled.");
                }
            });
        vp.add(p1);
        vp.add(p);
        confirmDeleteDialog.setWidget(vp);
    }

    void populateBackupTree(JSONArray clientInfo) {
        double size = 0;
        backupTree.removeItems();
        cbMap = new HashMap<String, Vector<CheckBox>>();
        int cs = clientInfo.size();
        totalHostsField.setText(Integer.toString(cs));
        for (int j=0; j < cs; j++) {
            JSONObject client = clientInfo.get(j).isObject();
            if (client == null) {
                continue;
            }
            TreeItem hroot = backupTree.addItem(client.get("host").isString().stringValue());
            String uuid = client.get("uuid").isString().stringValue();
            hroot.addItem("UUID:"+uuid);
            hroot.addItem("User:"+client.get("userid").isString().stringValue());
            if (client.get("backups") == null) {
                hroot.addItem("<No backups>");
            } else {
                CheckBox cb = new CheckBox("Backups");
			    
                cb.setFormValue(uuid+"/*");
                cb.addValueChangeHandler(this);
                Vector<CheckBox> cv = new Vector<CheckBox>();
                cv.addElement(cb);
                cbMap.put(uuid, cv);
                TreeItem broot = hroot.addItem(cb);
                JSONObject bobj;

                bobj = client.get("backups").isObject();
                Set<String> keys = bobj.keySet();
                Iterator<String> ki = keys.iterator();
                while (ki.hasNext()) {
                    String p = ki.next();
                    cb = new CheckBox(p);
                    cb.setFormValue(uuid+"/%"+p);
                    cb.addValueChangeHandler(this);
                    cv.addElement(cb);
                    TreeItem proot = broot.addItem(cb);
                    JSONArray versions = bobj.get(p).isArray();
                    if (versions != null) {
                        int s = versions.size();
                        for (int i=0; i < s; i++) {
                            JSONObject version = versions.get(i).isObject();
                            if (version != null) {
                                String status = version.get("status").isString().stringValue();
                                String hd = "i";
                                if ("done".equals(status)) {
                                    hd = "b";
                                }
                                Date d = new java.util.Date((long)(version.get("date").isNumber().doubleValue()));
                                cb = new CheckBox("<"+hd+">Date: "+d+"</"+hd+">", true);
                                cb.setFormValue(uuid+"/"+version.get("name").isString().stringValue());
                                cb.addValueChangeHandler(this);
                                cv.addElement(cb);
                                TreeItem vroot = proot.addItem(cb);
                                vroot.addItem("Status "+status);
                                vroot.addItem("Started: "+new java.util.Date((long)(version.get("start").isNumber().doubleValue())));
                                vroot.addItem("Size: "+version.get("size").isString().stringValue());
                                size += version.get("rsize").isNumber().doubleValue();
                            }
                        }
                    }
                }
            }
        }
        totalDiskUsageField.setText(formatSize((long)size));
    }

    String formatSize(long bytes) {
        try {
            long size = bytes;
            if (size > 1073741824) {
                String str = new Float((size + 52428.8) / 1073741824F).toString();
                return str.substring(0, str.indexOf(".") + 2) + " GB";
            } else if (size >= 1048576) {
                String str = new Float((size + 52428.8) / 1048576F).toString();
                return str.substring(0, str.indexOf(".") + 2) + " MB";
            } else if (size == 0) {
                return "0";
            } else {
                String str = new Float((size + 51.2) / 1024F).toString();
                return str.substring(0, str.indexOf(".") + 2) + " KB";
            }
        } catch (Exception e) {
            return "0 KB";
        }
    }

    void createBackupSummaryPanel(String menuTitle, MenuBar menu, int index) {
        VerticalPanel vp = new VerticalPanel();
        HTML help = new HTML("Shows a summary of available backups");
        Grid g = new Grid(2,3);

        help.setStyleName("backup-Help-Task");
        vp.add(help);
        backupDirectoryField.setVisibleLength(40);
        addRowToControlPanel(g, 0,
                             "Total hosts:",
                             totalHostsField,
                             "Total number of hosts that have created backups on this server.");
        addRowToControlPanel(g, 1,
                             "Total disk usage:",
                             totalDiskUsageField,
                             "Total amount of disk space consumed by all the backups. This number doesn't count shared files so it is a maximum size. If there are shared files then the actual disk usage is smaller.");
        g.setWidth("100%");
        g.setCellPadding(4);
        vp.add(g);
        backupsDeck.add(vp);
	
        backupSummaryIndex = index;
        menu.addItem(menuTitle, true, new Command() {
                public void execute() {
                    backupsDeck.showWidget(backupSummaryIndex);
                }
            });
    }

    void deleteSelectedBackups() {
        if (cbMap == null) {
            return;
        }
        backups = new Vector<String>();
        Iterator<String> keys = cbMap.keySet().iterator();
        while (keys.hasNext()) {
            Vector<CheckBox> cv = cbMap.get(keys.next());
            if (cv == null) {
                break;
            }
            if (cv.elementAt(0).getValue()) {
                // first element selects all the ones in the list
                backups.addElement(cv.elementAt(0).getFormValue());
            } else {
                int s = cv.size();
                boolean foundPath = false;
                for (int i=1; i < s; i++) {
                    CheckBox cb = cv.elementAt(i);
                    String fm = cb.getFormValue();
                    if (fm.indexOf('%') != -1) {
                        foundPath = cb.getValue();
                        if (foundPath) {
                            backups.addElement(fm);
                        }
                    } else if (!foundPath) {
                        if (cb.getValue()) {
                            backups.addElement(fm);
                        }
                    }
                }
            }
        }

        if (backups.size() == 0) {
            main.print("No backups to delete.");
        } else {
            if (confirmDeleteDialog == null) {
                createConfirmDeleteDialog();
            }
            confirmDeleteDialog.showRelativeTo(deleteBackups);
        }
    }

    void createBackupDetailPanel(String menuTitle, MenuBar menu, int index) {
        VerticalPanel vp = new VerticalPanel();
        HTML help = new HTML("Shows a details of available backups for a given host.");

        help.setStyleName("backup-Help-Task");
        vp.add(help);

        HorizontalPanel hp = new HorizontalPanel();

        VerticalPanel backupsPanel = new VerticalPanel();
        HorizontalPanel bph = new HorizontalPanel();
        bph.add(new Label("Available Backups:"));
        bph.add(new BackupPopupHelp("Shows a tree of all the available backup clients. Expanding each node shows some information for that backup client as well as a node labelled \"Backups\" that shows the various paths on that host that were backed up and all the versions available for each path"));
        backupsPanel.add(bph);
        DecoratorPanel dp = new DecoratorPanel();
        ScrollPanel sp = new ScrollPanel();
        sp.setHeight("300px");
        sp.setWidth("400px");
        sp.setAlwaysShowScrollBars(true);
        backupTree.addItem("<None>");
        sp.add(backupTree);
        dp.add(sp);
        backupsPanel.add(dp);
        hp.add(backupsPanel);

        VerticalPanel controlsPanel = new VerticalPanel();
        HorizontalPanel cph = new HorizontalPanel();
        cph.add(new Label("Actions:"));
        cph.add(new BackupPopupHelp("Allows you to apply actions to any selected nodes in the available backups tree."));
        controlsPanel.add(cph);
        deleteBackups = new Button("Delete Selected Backups");
        deleteBackups.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent evt) {
                    deleteSelectedBackups();
                }
            });
        controlsPanel.add(deleteBackups);
        //controlsPanel.add(new Button("View File List"));
        controlsPanel.setSpacing(4);

        hp.add(controlsPanel);

        hp.setSpacing(4);

        vp.setSpacing(4);

        vp.add(hp);
        vp.setWidth("100%");

        backupsDeck.add(vp);
	
        backupDetailIndex = index;
        menu.addItem(menuTitle, true, new Command() {
                public void execute() {
                    backupsDeck.showWidget(backupDetailIndex);
                }
            });
    }
}